using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities.ESGRatings;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;

namespace Sustainalytics.DataLayer.EsgRatings
{
    public class EsgInputFileRepository : IEsgInputFileRepository
    {
        private readonly IFileRepository _fileRepository;
        private readonly IRepositoryCollection<EsgInputFile> _esgInputFileRepository;

        public EsgInputFileRepository(IFileRepository fileRepository,
            IRepositoryCollection<EsgInputFile> repositoryCollection)
        {
            _fileRepository = fileRepository;
            _esgInputFileRepository = repositoryCollection;
        }

        /// <exception cref="System.ArgumentException">No instance of IFileRepository provided for creating an IEsgInputFileRepository!</exception>
        /// <exception cref="System.ArgumentException">No instance of IRepositoryCollection provided for creating an IEsgInputFileRepository!</exception>
        public static IEsgInputFileRepository GetInstance(IFileRepository fileRepository,
            IRepositoryCollection<EsgInputFile> repositoryCollection)
        {
            if (fileRepository == null)
            {
                throw new ArgumentException(
                    "No instance of IFileRepository provided for creating an IEsgInputFileRepository!");
            }

            if (repositoryCollection == null)
            {
                throw new ArgumentException(
                    "No instance of IRepositoryCollection provided for creating an IEsgInputFileRepository!");
            }

            return new EsgInputFileRepository(fileRepository, repositoryCollection);
        }

        public void AddFile(EsgInputFileComplete esgInputFile)
        {
            var newFile = this._fileRepository.UploadFile(esgInputFile.Id, esgInputFile.FileName, esgInputFile.ContentType);

            using (var fileContent = new MemoryStream(esgInputFile.Content))
            {
                fileContent.CopyTo(newFile.Item1);
                newFile.Item1.Flush();
                newFile.Item1.Close();
            }

            this._esgInputFileRepository.Create(new EsgInputFile
            {
                Id = esgInputFile.Id,
                AccountId = esgInputFile.AccountId,
                Name = esgInputFile.Name,
                Type = esgInputFile.Type,
                CreationTimestamp = DateTime.UtcNow,
                Processed = null
            });
        }

        public void DeleteFiles(EsgInputFileType fileType, string name, Guid accountId)
        {
            var esgInputFileIds = _esgInputFileRepository.ReadAllWhere(x => x.Type == fileType && x.Name == name && x.AccountId == accountId)
                .Select(x => x.Id).ToList();

            foreach (var id in esgInputFileIds)
            {
                _esgInputFileRepository.Delete(id);
            }
        }

        public IList<EsgInputFileComplete> ListLatestUnprocessedFiles(EsgInputFileType fileType)
        {
            var unprocessedFiles = this.ReadEsgInputFiles(fileType);

            foreach (var file in unprocessedFiles)
            {
                this.GetFileContent(file);
            }

            return unprocessedFiles;
        }

        private void GetFileContent(EsgInputFileComplete file)
        {
            var fileData = this._fileRepository.GetDownloadStream(file.Id);
            if (fileData == null) return;
            if (fileData.Item2 == null) return;
            using (var ms = new MemoryStream())
            {
                fileData.Item1.CopyTo(ms);// todo Petre - check that the stream exists before trying to touch it at
                file.Content = ms.ToArray();
            }

            file.FileName = fileData.Item2;
            file.ContentType = fileData.Item3;
        }

        private IList<EsgInputFileComplete> ReadEsgInputFiles(EsgInputFileType fileType)
        {
            var unprocessedFiles = this._esgInputFileRepository.ReadAllWhere(f => f.Processed == null && f.Type == fileType)
                //.GroupBy(x => new { x.AccountId, x.Name })
                //.Select(fg => fg.OrderByDescending(f => f.CreationTimestamp).First())
                .OrderBy(f => f.CreationTimestamp)
                .Select(f => new EsgInputFileComplete
                {
                    Id = f.Id,
                    AccountId = f.AccountId,
                    Name = f.Name,
                    Type = f.Type,
                    CreationTimestamp = f.CreationTimestamp,
                    Processed = f.Processed
                })
                .ToList();

            return unprocessedFiles;
        }

        public IList<EsgInputFileComplete> ReadEsgInputFilesByAccountId(EsgInputFileType fileType, Guid? accountId)
        {
            var unprocessedFiles = this._esgInputFileRepository.ReadAllWhere(f => f.Type == fileType && f.Processed == null && f.AccountId == accountId)
                .OrderBy(f => f.CreationTimestamp)
                .Select(f => new EsgInputFileComplete
                {
                    Id = f.Id,
                    AccountId = f.AccountId,
                    Name = f.Name,
                    Type = f.Type,
                    CreationTimestamp = f.CreationTimestamp,
                    Processed = f.Processed
                })
                .ToList();

            return unprocessedFiles;
        }

        public void MarkFileAsProcessed(Guid fileId)
        {
            var file = this._esgInputFileRepository.Read(fileId);

            file.Processed = DateTime.UtcNow;

            this._esgInputFileRepository.Update(fileId, file);
        }

        public EsgInputFileComplete GetESGFilesByFilter(Expression<Func<EsgInputFile, bool>> wherePredicate)
        {
            var unprocessedFile = this._esgInputFileRepository.ReadAllWhere(wherePredicate)
                .GroupBy(f => f.Name.ToLowerInvariant())
                .Select(fg => fg.OrderByDescending(f => f.CreationTimestamp).First())
                .OrderBy(f => f.CreationTimestamp)
                .Select(f => new EsgInputFileComplete
                {
                    Id = f.Id,
                    Type = f.Type,
                    Name = f.Name,
                    AccountId = f.AccountId,
                    CreationTimestamp = f.CreationTimestamp,
                    Processed = f.Processed
                })
                .FirstOrDefault();

            if (unprocessedFile != null)
            {
                this.GetFileContent(unprocessedFile);
            }
            return unprocessedFile;
        }
    }
}
